#include <fmt/format.h>
#include <filesystem>
#include <cstring>

#include "util/local_store.h"

using namespace kawauso;
using namespace std;

#define STORE_DIR "store/"

LocalStore::LocalStore(const string &filename) {

    filesystem::path directory(STORE_DIR);
    if (!filesystem::exists(directory)) {
        filesystem::create_directories(directory);
    }

    string filepath = STORE_DIR + filename;

    fileStream = filesystem::exists(filesystem::path(filepath))
            ? fopen(filepath.c_str(), "rb+")
            : fopen(filepath.c_str(), "wb+");

    if (fileStream == nullptr) {
        throw std::runtime_error(fmt::format(
                "Failed to open new StoreFile, reason: {}", strerror(errno)));
    }

    if (!loadRecordFromFile()) {
        rewind(fileStream);
        for (auto &pair : pairs) {
            writeToFile(pair);
        }
        if (truncate(filepath.c_str(), ftell(fileStream)) != 0) {
            throw std::runtime_error(fmt::format(
                    "Failed to truncate StoreFile, reason: {}", strerror(errno)));
        }
    }
}

LocalStore::~LocalStore() {
    fclose(fileStream);
}

bool LocalStore::readBufferFromFile(string &buffer, int length) {
    for (int i = 0, c; i < length; i++) {
        if ((c = fgetc_unlocked(fileStream)) != EOF) {
            buffer.push_back((char)c);
        } else {
            return false;
        }
    }
    return true;
}

bool LocalStore::loadRecordFromFile() {

    string keyBuffer;
    string valueBuffer;
    bool readKey = true;
    int c;

    while ((c = fgetc_unlocked(fileStream)) != EOF) {
        if (readKey) {
            if (!readBufferFromFile(keyBuffer, c)) {
                return false;
            }
            readKey = false;
        } else {
            if (!readBufferFromFile(valueBuffer, c)) {
                return false;
            }
            if (c > 0) {
                pairs[keyBuffer] = valueBuffer;
            }
            keyBuffer.clear();
            valueBuffer.clear();
            readKey = true;
        }
    }

    return true;
}

template<>
const string& LocalStore::get<string>(const string &key) {
    return pairs[key];
}

template<>
void LocalStore::set<string>(const string &key, const string &value) {
    pairs[key] = value;
    writeToFile({key, value});
}

bool LocalStore::exists(const string &key) {
    return pairs.find(key) != pairs.end();
}

void LocalStore::remove(const string &key) {
    if (pairs.erase(key) == 0) {
        return;
    }
    writeToFile({key, ""});
}

void LocalStore::writeToFile(const pair<const string, string> &pair) {
    if (fputc_unlocked(pair.first.length(), fileStream) == EOF ||
        fwrite_unlocked(pair.first.data(), sizeof(char), pair.first.length(), fileStream) == EOF ||
        fputc_unlocked(pair.second.length(), fileStream) == EOF ||
        fwrite_unlocked(pair.second.data(), sizeof(char), pair.second.length(), fileStream) == EOF) {
        throw std::runtime_error(fmt::format(
                "Failed to write StoreFile, reason: {}", strerror(errno)));
    }
}
